package act.conf;

/*-
 * #%L
 * ACT Framework
 * %%
 * Copyright (C) 2014 - 2017 ActFramework
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import act.Act;
import act.app.App;
import act.handler.UnknownHttpMethodProcessor;
import act.view.TemplatePathResolver;
import act.view.View;
import org.osgl.$;
import org.osgl.exception.NotAppliedException;
import org.osgl.util.E;
import org.osgl.util.S;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * {@link App} configuration keys. General rules:
 * <p/>
 * <ul>
 * <li>When a key is ended with <code>.enabled</code>, then you should be able to set
 * the setting without <code>.enabled</code> or replace it with <code>.disabled</code>
 * but the value will be inverted. For example, <code>built_in.transformer.enabled</code>
 * is equal to <code>built_in.transformer</code> and invert to
 * <code>built_in.transformer.disabled</code></li>
 * <p/>
 * <li>When a key is ended with <code>.impl</code>, then you can either put an instance into
 * the configuration map or a string of the class className</li>
 * </ul>
 */
public enum AppConfigKey implements ConfigKey {

    /**
     * {@code act.basic_authentication.enabled} turn on/off Basic Authentication
     * in Act application.
     *
     * Default value: `false`
     *
     * **Note** there is no logic around this configuration in the core
     * ActFramework. It is up to the security plugins like `act-aaa-plugin`
     * to favor the value of this setting
     */
    BASIC_AUTHENTICATION("basic_authentication.enabled"),


    /**
     * {@code act.cache.impl}
     * Specify {@link org.osgl.cache.CacheServiceProvider Cache service provider}
     * <p>Default value: {@link org.osgl.cache.CacheServiceProvider.Impl#Simple the simple
     * in memory map based cache service implementation}</p>
     */
    CACHE_IMPL("cache.impl"),

    /**
     * {@code act.cache.name}
     *
     * Specify the default cache name
     *
     * Default value: `_act_app_`
     */
    CACHE_NAME("cache.name"),

    /**
     * {@code act.cache.name.session}
     *
     * Specify the session cache name
     *
     * Default value: the value configured by {@link #CACHE_NAME}
     */
    CACHE_NAME_SESSION("cache.name.session"),

    /**
     * {@code act.cli.enabled}
     *
     * Turn on/off CLI server support
     *
     * Default value: {@code true}
     */
    CLI_ENABLED("cli.enabled"),

    /**
     * {@code act.cli.port} specifies the default cli (telnet) port the application
     * listen to.
     * <p>Default value: {@code 5461}</p>
     */
    CLI_PORT("cli.port"),

    /**
     * {@code act.cli.json.page.size}
     * Specify the maximum records in one page for JSON layout by CLI command
     *
     * Default value: 10
     */
    CLI_PAGE_SIZE_JSON("cli.page.size.json"),

    /**
     * {@code act.cli.table.page.size}
     * Specify the maximum records in one page for table layout by CLI command
     *
     * Default value: 22
     */
    CLI_PAGE_SIZE_TABLE("cli.page.size.table"),

    /**
     * {@code cli.session.expiration} specifies the number of seconds
     * a cli session can exists after last user interaction
     * <p>Default value: {@code 300} seconds. e.g. 5 minutes</p>
     */
    CLI_SESSION_EXPIRATION("cli.session.expiration"),

    /**
     * {@code cli.session.max} specifies the maximum number of cli threads
     * can exists concurrently
     * <p>Default value: {@code 3}</p>
     */
    CLI_SESSION_MAX("cli.session.max"),


    /**
     * `act.cli_over_http.enabled` turn on/off CLI over http feature, which
     * allows ActFramework to handle http request sent through to the  {@link #CLI_OVER_HTTP_PORT}
     * as a way to invoke CLI commands and inspect results
     *
     * Default value: `false`
     */
    CLI_OVER_HTTP("cli_over_http.enabled"),

    /**
     * `act.cli_over_http.authority` specifies the {@link act.cli.CliOverHttpAuthority} implementation
     */
    CLI_OVER_HTTP_AUTHORITY("cli_over_http.authority.impl"),

    /**
     * `act.cli_over_http.port` specifies the default cli over http port the application
     * listen to.
     *
     * Default value: `5462`
     */
    CLI_OVER_HTTP_PORT("cli_over_http.port"),

    /**
     * `act.cli_over_http.port` specify the title to be displayed on the CLI Over Http
     * page
     *
     * Default value: "Cli Over Http"
     */
    CLI_OVER_HTTP_TITLE("cli_over_http.title"),

    /**
     * `act.cli_over_http.syscmd.enabled` turn on/off system command on CLI Over Http
     * page
     *
     * Default value: `true`
     */
    CLI_OVER_HTTP_SYS_CMD("cli_over_http.syscmd.enabled"),

    /**
     * `act.cookie.domain_provider.impl` specify the provider
     * that provides the cookie domain name
     *
     * Default value: value of {@link #HOST}
     */
    COOKIE_DOMAIN_PROVIDER("cookie.domain_provider.impl"),

    /**
     * {@code cookie.prefix} specifies the prefix to be prepended
     * to the different cookie names e.g. session cookie, flash cookie,
     * locale cookie etc. Let's say the default cookie name is
     * {@code act_session}, and user specifies the prefix {@code my_app}
     * then the session cookie name will be {@code my_app_session}.
     * <p>Note this setting also impact the {@link AppConfig#flashCookieName()}</p>
     * <p>Default value: {@code act}</p>
     */
    COOKIE_PREFIX("cookie.prefix"),

    /**
     * {@code act.cors.enabled} turn on/off CORS in Act application
     *
     * Default value: `false`
     */
    CORS("cors.enabled"),

    /**
     * `act.cors.option.check` specify whether the framework should
     * check the current request is an HTTP OPTION method before applying
     * controller headers or not
     *
     * default value: `true`
     */
    CORS_CHECK_OPTION_METHOD("cors.option.check.enabled"),

    /**
     * {@code act.cors.origin} specifies `Access-Control-Allow-Origin` header
     * to be output
     *
     * Default value: `*`
     */
    CORS_ORIGIN("cors.origin"),

    /**
     * {@code act.cors.headers} specifies both `Access-Control-Expose-Headers`
     * and `Access-Control-Allow-Headers`
     *
     * Default value: `Content-Type, X-HTTP-Method-Override`
     */
    CORS_HEADERS("cors.headers"),

    /**
     * {@code act.cors.headers.expose} specify `Access-Control-Expose-Headers`.
     * Note this setting will overwrite the setting of {@link #CORS_HEADERS} if
     * it is set
     *
     * Default value: empty
     */
    CORS_HEADERS_EXPOSE("cors.headers.expose"),

    /**
     * {@code act.cors.headers.expose} specify `Access-Control-Allow-Headers`.
     * Note this setting will overwrite the setting of {@link #CORS_HEADERS} if
     * it is set
     *
     * Default value: empty
     */
    CORS_HEADERS_ALLOWED("cors.headers.allowed"),

    /**
     * {@code act.cors.max_age} specifies `Access-Control-Max-Age`.
     *
     * Default value: 30*60 (seconds)
     */
    CORS_MAX_AGE("cors.max_age"),

    /**
     * `act.cors.allow_credential` specifies `Access-Control-Allow-Credential`.
     *
     * Default value: `false`
     */
    CORS_ALLOW_CREDENTIALS("cors.allow_credentials.enabled"),

    /**
     * {@code act.content_suffix.aware.enabled}
     * <p>
     *     Once enabled then the framework automatically recognize request with content suffix.
     *     E.g. {@code /customer/123/json} or {@code /customer/123.json} will match the
     *     route {@code /customer/123} and set the request {@code Accept} header to
     *     {@code application/json}
     * </p>
     * <p>Default value: {@code false}</p>
     */
    CONTENT_SUFFIX_AWARE("content_suffix.aware.enabled"),

    /**
     * {@code act.csrf.enabled} turn on/off global CSRF protect
     *
     * Default value: `true`
     */
    CSRF("csrf.enabled"),

    /**
     * {@code act.csrf.param_name} specifies the http request param name
     * used to convey the csrf token
     *
     * Default value: the value of {@link act.app.ActionContext#ATTR_CSRF_TOKEN}
     */
    CSRF_PARAM_NAME("csrf.param_name"),

    /**
     * {@code act.csrf.header_name} specifies name of the http request
     * header used to convey the csrf token sent from AJAX client.
     *
     * Default value: `X-Xsrf-Token` - the name used by AngularJs
     */
    CSRF_HEADER_NAME("csrf.header_name"),

    /**
     * {@code act.csrf.cookie_name} specify the name of the cookie used
     * to convey the csrf token generated on the server for the first GET
     * request coming from a client.
     *
     * Default value: `XSRF-TOKEN` - the name used by AngularJs
     */
    CSRF_COOKIE_NAME("csrf.cookie_name"),

    /**
     * `act.csrf.protector.impl` specifies the implementation of
     * {@link act.security.CSRFProtector}.
     *
     * The value of this configuration could be either a name of
     * the class that implements {@link act.security.CSRFProtector}
     * interface, or the enum name of {@link act.security.CSRFProtector.Predefined}
     *
     * Default value: `HMAC` which specifies the {@link act.security.CSRFProtector.Predefined#HMAC}
     */
    CSRF_PROTECTOR("csrf.protector.impl"),

    /**
     * `act.db.seq_gen.impl` specifies the implementation of
     * {@link act.db.util._SequenceNumberGenerator}.
     *
     * Default value: `null`
     */
    DB_SEQ_GENERATOR("db.seq_gen.impl"),

    /**
     * `dsp.token` specifies the name of "double submission protect token"
     *
     * Default value: `act_dsp_token`
     */
    DOUBLE_SUBMISSION_PROTECT_TOKEN("dsp.token"),

    /**
     * {@code act.encoding} specifies application default encoding
     * <p>Default value: utf-8</p>
     */
    ENCODING("encoding"),

    /**
     * `act.enum.resolving.case_sensitive` specifies whether it
     * allow enum resolving for request parameters to ignore case
     *
     * Default value: `false` meaning enum resolving is case insensitive
     */
    ENUM_RESOLVING_CASE_SENSITIVE("enum.resolving.case_sensitive"),

    /**
     * {@code act.fmt.date} specifies the default date format used to
     * lookup/output the date string
     * <p>Default value: the pattern of {@code java.text.DateFormat.getDateInstance()}</p>
     */
    FORMAT_DATE("fmt.date"),


    /**
     * {@code act.fmt.date} specifies the default date and time format used to
     * lookup/output the date string
     * <p>Default value: the pattern of {@code java.text.DateFormat.getDateTimeInstance()}</p>
     */
    FORMAT_DATE_TIME("fmt.date_time"),

    /**
     * {@code act.fmt.time} specifies the default time format used to
     * lookup/output the date time string
     * <p>Default value: the pattern of {@code java.text.DateFormat.getTimeInstance()}</p>
     */
    FORMAT_TIME("fmt.time"),

    /**
     * `act.handler.csrf_check_failure.impl` specifies the implementation
     * for {@link act.util.MissingAuthenticationHandler}
     *
     * Default value: {@link act.util.RedirectToLoginUrl}
     */
    HANDLER_CSRF_CHECK_FAILURE("handler.csrf_check_failure.impl"),

    /**
     * `act.handler.csrf_check_failure.ajax.impl` specifies the implementation for
     * {@link act.util.MissingAuthenticationHandler} dealing with the case of AJAX
     * request
     *
     * Default value: the value of {@link #HANDLER_CSRF_CHECK_FAILURE}
     */
    HANDLER_AJAX_CSRF_CHECK_FAILURE("handler.csrf_check_failure.ajax.impl"),

    /**
     * {@code handler.missing_authentication.impl} specifies the implementation
     * for {@link act.util.MissingAuthenticationHandler}
     * <p>Default value: {@link act.util.RedirectToLoginUrl}</p>
     */
    HANDLER_MISSING_AUTHENTICATION("handler.missing_authentication.impl"),

    /**
     * {@code handler.missing_authentication.ajax.impl} specifies the implementation
     * for {@link act.util.MissingAuthenticationHandler} dealing with the case of AJAX
     * request
     * <p>Default value: the value of {@link #HANDLER_MISSING_AUTHENTICATION}</p>
     */
    HANDLER_MISSING_AUTHENTICATION_AJAX("handler.missing_authentication.ajax.impl"),

    /**
     * {@code unknown_http_method_handler} specifies a class/instance that
     * implements {@link UnknownHttpMethodProcessor} that process
     * the HTTP methods that are not recognized by {@link act.route.Router},
     * e.g. "OPTION", "PATCH" etc
     */
    HANDLER_UNKNOWN_HTTP_METHOD("handler.unknown_http_method.impl"),

    /**
     * {@code act.host} specifies the host the application
     * reside on.
     * <p/>
     * <p>Default value: {@code localhost}</p>
     */
    HOST("host"),

    /**
     * `act.http.external_server.enabled` specify if the app is running behind a front end
     * http server
     *
     * Default value: `true` when running in PROD mode; `false` when running in DEV mode
     */
    HTTP_EXTERNAL_SERVER("http.external_server.enabled"),

    /**
     * {@code act.http.params.max} specifies the maximum number of http parameters
     * this is to prevent the hash collision DOS attack
     * <p>Default value: {@code 128}</p>
     */
    HTTP_MAX_PARAMS("http.params.max"),

    /**
     * {@code act.http.port} specifies the default http port the application
     * listen to
     * <p/>
     * <p>Default value: {@code 5460}</p>
     */
    HTTP_PORT("http.port"),

    /**
     * `act.http.port.external` set the external port which is used to
     * construct the full url.
     *
     * Note act does not listen to external port directly. The recommended
     * pattern is to have a front end HTTP server (e.g. nginx) to handle
     * the external request and forward to act
     *
     * Default value: `80`
     */
    HTTP_EXTERNAL_PORT("http.port.external"),

    /**
     * `act.http.port.external.secure` set the external secure port which is
     * used to construct full url string when app is running secure mode
     *
     * @see #HTTP_EXTERNAL_PORT
     */
    HTTP_EXTERNAL_SECURE_PORT("http.port.external.secure"),

    /**
     * {@code act.http.secure} specifies whether the default http port is
     * running https or http.
     * <p></p>
     * <p>
     *     Default value: {@code false} when Act is running in dev mode
     *     or {@code true} when Act is running in prod mode
     * </p>
     */
    @SuppressWarnings("unchecked")
    HTTP_SECURE("http.secure.enabled"),

    /**
     * `act.i18n.enabled` turn on/off i18n tools, e.g. {@link act.i18n.LocaleResolver}
     *
     * Default value: `false`
     */
    I18N("i18n.enabled"),

    /**
     * `act.i18n.locale.param_name` specifies the param name to set client locale in http request
     *
     * Default value: `act_locale`
     */
    I18N_LOCALE_PARAM_NAME("i18n.locale.param_name"),

    /**
     * `act.i18n.locale.cookie_name` specifies the name for the locale cookie
     *
     * Default value: `act_locale`
     */
    I18N_LOCALE_COOKIE_NAME("i18n.locale.cookie_name"),

    /**
     * {@code act.idgen.node_id.provider.impl} specifies the {@link act.util.IdGenerator.NodeIdProvider}
     * implementation for {@link App#idGenerator}
     * <p>Default value: {@link act.util.IdGenerator.NodeIdProvider.IpProvider}</p>
     */
    ID_GEN_NODE_ID_PROVIDER("idgen.node_id.provider.impl"),

    /**
     * {@code act.idgen.node_id.effective_ip_bytes} specifies how many bytes in the ip address
     * will be used to calculate node ID. Usually in a cluster environment, the ip address will
     * be different at only (last) one byte or (last) two bytes, in which case it could set this
     * configuration to {@code 1} or {@code 2}. When the configuration is set to {@code 4} then
     * it means all 4 IP bytes will be used to calculate the node ID
     * <p>Default value: {@code 4}</p>
     */
    ID_GEN_NODE_ID_EFFECTIVE_IP_BYTES("idgen.node_id.effective_ip_bytes.size"),

    /**
     * {@code act.idgen.start_id.provider.impl} specifies the {@link act.util.IdGenerator.StartIdProvider}
     * implementation for {@link App#idGenerator}
     * <p>Default value: {@link act.util.IdGenerator.StartIdProvider.DefaultStartIdProvider}</p>
     */
    ID_GEN_START_ID_PROVIDER("idgen.start_id.provider.impl"),

    /**
     * {@code act.idgen.start_id.file} specifies the start id persistent file for
     * {@link act.util.IdGenerator.StartIdProvider.FileBasedStartCounter}
     * <p>Default value: {@code .act.id-app}</p>
     */
    ID_GEN_START_ID_FILE("idgen.start_id.file"),

    /**
     * {@code act.idgen.seq_id.provider.impl} specifies the {@link act.util.IdGenerator.SequenceProvider}
     * implementation for {@link App#idGenerator}
     * <p>Default value: {@link act.util.IdGenerator.SequenceProvider.AtomicLongSeq}</p>
     */
    ID_GEN_SEQ_ID_PROVIDER("idgen.seq_id.provider.impl"),

    /**
     * {@code act.idgen.encoder.impl} specifies the {@link act.util.IdGenerator.LongEncoder}
     * implementation for {@link App#idGenerator}
     * <p>Default value: {@link act.util.IdGenerator.SafeLongEncoder}</p>
     */
    ID_GEN_LONG_ENCODER("idgen.encoder.impl"),

    /**
     * {@code job.pool.size} specifies the maximum number of threads
     * can exists in the application's job manager's thread pool
     * <p>Default value: {@code 10}</p>
     */
    JOB_POOL_SIZE("job.pool.size"),

    /**
     * {@code act.locale} specifies the application default locale
     * <p>Default value: {@link java.util.Locale#getDefault}</p>
     */
    LOCALE("locale") {
        @Override
        public <T> T val(Map<String, ?> configuration) {
            Object o = super.val(configuration);
            if (null == o) {
                return null;
            }
            if (o instanceof String) {
                return (T) Locale.forLanguageTag((String) o);
            } else if (o instanceof Locale) {
                return (T) o;
            } else {
                String s = o.toString();
                return (T) Locale.forLanguageTag(s);
            }
        }
    },

    /**
     * {@code act.metric.enabled}
     * Turn on/off metric in Act application
     *
     * Default value: {@code true}
     */
    METRIC_ENABLED("metric.enabled"),

    /**
     * {@code act.modules}
     *
     * Declare additional app base (for maven modules)
     *
     * Default value: `null`
     */
    MODULES("modules"),

    /**
     * {@code act.namedPorts} specifies a list of port names this
     * application listen to. These are additional ports other than
     * the default {@link #HTTP_PORT}
     * <p/>
     * The list is specified as
     * <pre><code>
     * act.namedPorts=admin:8888;ipc:8899
     * </code></pre>
     * <p>Default value: {@code null}</p>
     * <p>Note, the default port that specified in {@link #HTTP_PORT} configuration
     * and shall not be specified in this namedPorts configuration</p>
     */
    NAMED_PORTS("namedPorts"),

    /**
     * {@code ping.path} specify the ping path.
     * If this setting is specified, then when session resolving, system
     * will check if the current URL matches the setting. If matched
     * then session cookie expiration time will not be changed. Otherwise
     * the expiration time will refresh
     * <p>Default value: {@code null}</p>
     */
    PING_PATH("ping.path"),

    /**
     * {@code profile} specifies the profile to load configuration
     * If this setting is specified, and there is a folder named as
     * the {@code profile} setting sit under {@code /resource/conf}
     * folder, then the properties files will be loaded from
     * that folder.
     * <p>Default value: the value of the {@link Act#mode()}</p>
     * <p>Note, unlike other configuration items which is usually specified
     * in the configuration file. {@code profile} setting is load
     * by {@link System#getProperty(String)}</p>, thus it is usually
     * specified with JVM argument {@code -Dprofile=<profile>}
     */
    PROFILE("profile"),

    /**
     * `render.json.output_charset`
     *
     * Specifies output charset in `application/json` response header `Content-Type`
     *
     * Default value: `false`
     */
    RENDER_JSON_OUTPUT_CHARSET("render.json.output_charset.enabled"),

    /**
     * `render.json.content_type.ie`
     *
     * Internet Explorer is know to have an issue with `application/json` content type.
     * if this configuration is set, the framework will output Content-Type header using
     * the setting when the request is detected as initialized from IE browser.
     *
     * Default value: `null`
     */
    RENDER_JSON_CONTENT_TYPE_IE("render.json.content_type.ie"),


    /**
     * {@code resolver.error_template_path.impl} specifies error page (template)
     * path resolver implementation
     * <p>Default value: {@code act.util.ErrorTemplatePathResolver.DefaultErrorTemplatePathResolver}</p>
     */
    RESOLVER_ERROR_TEMPLATE_PATH("resolver.error_template_path.impl"),


    /**
     * {@code resolver.template_path.impl} specifies the class that
     * implements {@link TemplatePathResolver}. Application
     * developer could use this configuration to add some flexibility to
     * template path resolving logic, e.g. different home for different locale
     * or different home for different device type etc.
     * <p/>
     * <p>Default value: {@link TemplatePathResolver}</p>
     */
    RESOLVER_TEMPLATE_PATH("resolver.template_path.impl"),

    /**
     * `resource.preload.size.limit`
     *
     * Specifies the maximum number of bytes of a resource that can be preload into memory.
     * Specifies `0` or negative number to disable resource preload feature
     *
     * Default value: `1024 * 10`, i.e. 10KB
     */
    RESOURCE_PRELOAD_SIZE_LIMIT("resource.preload.size.limit"),

    /**
     * {@code scan_package}
     * Specify the app package in which all classes is subject
     * to bytecode processing, e.g enhancement and injection.
     * This setting should be specified when application loaded.
     * Otherwise Act will try to process all classes found in
     * application's lib and classes folder, which might cause
     * performance issue on loading
     */
    SCAN_PACKAGE("scan_package"),

    /**
     * {@code secret}
     * Specifies the secret key the application used to do general
     * encrypt/decrypt/sign etc
     * <p>Default value: {@code myawesomeapp}</p>
     */
    SECRET("secret"),

    /**
     * {@code server.header}
     * Specifies the server header to be output to the response
     * <p>Default value: {@code act}</p>
     */
    SERVER_HEADER("server.header"),

    /**
     * {@code session.ttl} specifies the session duration in seconds.
     * If user failed to interact with server for amount of time that
     * exceeds the setting then the session will be destroyed
     *
     * <p>Default value: {@code 60 * 30} i.e half an hour</p>
     */
    SESSION_TTL("session.ttl"),

    /**
     * {@code session.persistent.enabled}
     * Specify whether the system
     * should treat session cookie as persistent cookie. If this setting
     * is enabled, then the user's session will not be destroyed after
     * browser closed.
     *
     * <p>Default value: {@code false}</p>
     *
     * See <a href="http://en.wikipedia.org/wiki/HTTP_cookie#Persistent_cookie">HTTP_cookie</a>
     */
    SESSION_PERSISTENT_ENABLED("session.persistent.enabled"),

    /**
     * {@code session.encrypted.enabled} specify whether the system should
     * encrypt the key/value pairs in the session cookie. Enable session
     * encryption will greatly improve the security but with the cost
     * of additional CPU usage and a little bit longer time on request
     * processing.
     *
     * <p>Default value: {@code false}</p>
     */
    SESSION_ENCRYPT_ENABLED("session.encrypt.enabled"),

    /**
     * `act.session.key.username` specifies the session key for username
     *
     * Default value: `username`
     */
    SESSION_KEY_USERNAME("session.key.username"),

    /**
     * {@code session.mapper.impl} specify the implementation of {@link act.util.SessionMapper}
     *
     * <p>Default value: {@code act.util.SessionMapper.DefaultSessionMapper}</p>
     */
    SESSION_MAPPER("session.mapper.impl"),

    /**
     * {@code session.secure.enabled} specifies whether the session cookie should
     * be set as secure. Enable secure session will cause session cookie only
     * effective in https connection. Literally this will enforce the web site to run
     * default by https.
     *
     * <p>Default value: {@code true}</p>
     *
     * <p><b>Note</b> when {@link Act Act server} is running in {@link Act.Mode#DEV mode}
     * session http only will be disabled without regarding to the {@code session.secure.enabled}
     * setting</p>
     */
    SESSION_SECURE("session.secure.enabled"),

    /**
     * {@code act.source.version} specifies the java version
     * of the src code. This configuration is used only
     * in dev mode.
     * <p>Default value: 1.7</p>
     */
    SOURCE_VERSION("source.version"),

    /**
     * {@code act.target.version} specifies the java version
     * of the compile target code. This configuration is used only
     * in dev mode.
     * <p>Default value: 1.7</p>
     */
    TARGET_VERSION("target.version"),

    /**
     * {@code template.home} specifies where the view templates resides.
     * If not specified then will use the {@link View#name() view name
     * in lower case} as the template home if that view is used.
     * <p/>
     * <p>Default value: {@code default}</p>
     */
    TEMPLATE_HOME("template.home"),

    /**
     * `upload.file.download.enabled`
     *
     * Turn on/off downloader for uploading files
     *
     * Default value: `false`
     */
    UPLOAD_FILE_DOWNLOAD("upload.file.download.enabled"),

    /**
     * `act.url.context` specifies the app URL context.
     *
     * If this configuration is specified then all route configured will
     * be attached to the configured context path.
     *
     * Default value: `null`
     */
    URL_CONTEXT("url.context"),

    /**
     * {@code url.login} specifies the login URL which is used
     * by {@link act.util.RedirectToLoginUrl}
     * <p>Default value: {@code /login}</p>
     */
    URL_LOGIN("url.login"),

    /**
     * {@code url.login.ajax} specifies the login URL which is used
     * by {@link act.util.RedirectToLoginUrl} when request is AJAX
     * <p>Default value: the value of {@link #URL_LOGIN}</p>
     */
    URL_LOGIN_AJAX("url.login.ajax"),

    /**
     * {@code act.view.default} specifies the default view solution. If there
     * are multiple views registered and default view are available, then
     * it will be used at priority to load the templates
     * <p/>
     * <p>Default value: {@code rythm}</p>
     */
    VIEW_DEFAULT("view.default"),

    X_FORWARD_PROTOCOL("x_forward_protocol"),

    ;
    private String key;
    private Object defVal;
    static ConfigKeyHelper helper = new ConfigKeyHelper(Act.F.MODE_ACCESSOR);

    AppConfigKey(String key) {
        this(key, null);
    }

    AppConfigKey(String key, Object defVal) {
        this.key = key;
        this.defVal = defVal;
    }

    public static void onApp(final App app) {
        helper.classLoaderProvider(new $.F0<ClassLoader>() {
            @Override
            public ClassLoader apply() throws NotAppliedException, $.Break {
                return app.classLoader();
            }
        });
    }

    /**
     * Return the key string
     *
     * @return the key of the configuration
     */
    public String key() {
        return key;
    }

    /**
     * Return default value of this setting. The configuration data map
     * is passed in in case the default value be variable depending on
     * another setting.
     *
     * @param configuration
     * @return return the default value
     */
    protected Object getDefVal(Map<String, ?> configuration) {
        return defVal;
    }

    /**
     * Calling to this method is equals to calling {@link #key()}
     *
     * @return key of the configuration
     */
    @Override
    public String toString() {
        return key;
    }

    @Override
    public Object defVal() {
        return defVal;
    }

    public <T> List<T> implList(String key, Map<String, ?> configuration, Class<T> c) {
        return helper.getImplList(key, configuration, c);
    }

    /**
     * Return configuration value from the configuration data map using the {@link #key}
     * of this {@link AppConfigKey setting} instance
     *
     * @param configuration
     * @param <T>
     * @return return the configuration
     */
    public <T> T val(Map<String, ?> configuration) {
        return helper.getConfiguration(this, configuration);
    }

    private static Map<String, AppConfigKey> lookup = new HashMap<String, AppConfigKey>(50);

    static {
        for (AppConfigKey k : values()) {
            lookup.put(k.key().toLowerCase(), k);
        }
    }

    /**
     * Return key enum instance from the string in case insensitive mode
     *
     * @param s
     * @return configuration key from the string
     */
    public static AppConfigKey valueOfIgnoreCase(String s) {
        E.illegalArgumentIf(S.blank(s), "config key cannot be empty");
        return lookup.get(s.trim().toLowerCase());
    }

}
